package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

type User struct {
	ID             int
	Addr           string
	EnterAt        time.Time
	MessageChannel chan string
}

type Message struct {
	OwnerID int
	Content string
}

func (u User) String() string {
	return strconv.Itoa(u.ID)
}

var (
	//新用户到来，通过该channel登记
	enteringChannel = make(chan *User)
	//用户离开，通过该channel登记
	leavingChannel = make(chan *User)
	//广播用户的普通消息channel，缓冲是尽可能避免出现异常情况阻塞，这里设置了8
	//具体值通过情况调整
	messageChannel = make(chan *Message, 8)
)

// broadcaster：用于记录聊天室用户，并进行消息广播
// 1. 新用户进来 2. 用户普通消息 3. 用户离开
func broadcaster() {
	users := make(map[*User]struct{})

	for {
		select {
		case user := <-enteringChannel:
			//新用户进入
			users[user] = struct{}{}
		case user := <-leavingChannel:
			//用户离开
			delete(users, user)
			//避免goroutine泄露
			close(user.MessageChannel)
		case msg := <-messageChannel:
			for user := range users {
				if user.ID == msg.OwnerID {
					continue
				}

				user.MessageChannel <- msg.Content
			}
		}
	}

}

var i = 0

func GetUserID() int {
	i = i + 1
	return i
}

func sendMessage(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

func handleConn(conn net.Conn) {
	defer conn.Close()
	//1.新用户寄来，构建该用户实例
	user := &User{
		ID:             GetUserID(),
		Addr:           conn.RemoteAddr().String(),
		EnterAt:        time.Now(),
		MessageChannel: make(chan string, 8),
	}

	//2. 由于当前是在一个新的goroutine中进行读操作，
	// 所以需要开一个goroutine用于写操作，读写goroutine之间可以通过channel通信
	go sendMessage(conn, user.MessageChannel)

	//3. 给当前用户发送欢迎信息，向所有用户告知新用户到来
	user.MessageChannel <- "Welcome, " + user.String()
	msg := &Message{
		OwnerID: user.ID,
		Content: fmt.Sprintf("user: %d has enter", user.ID),
	}
	messageChannel <- msg

	//4. 户口带全局用户队列中，避免用锁
	enteringChannel <- user
	var userActive = make(chan struct{})
	go func() {
		d := 5 * time.Minute
		timer := time.NewTimer(d)
		for {
			select {
			case <-timer.C:
				conn.Close()
			case <-userActive:
				timer.Reset(d)
			}
		}
	}()

	//5. 循环读取用户输入
	input := bufio.NewScanner(conn)
	for input.Scan() {
		in_msg := &Message{
			OwnerID: user.ID,
			Content: fmt.Sprintf("%d: ", user.ID) + input.Text(),
		}
		messageChannel <- in_msg
		//用户活跃
		userActive <- struct{}{}
	}

	if err := input.Err(); err != nil {
		log.Println("读取错误： ", err)
	}

	//6. 用户离开
	leavingChannel <- user

}

func main() {
	listener, err := net.Listen("tcp", ":8081")
	if err != nil {
		panic(err)
	}

	go broadcaster()

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
			continue
		}

		go handleConn(conn)
	}
}
